package
{
	import ConfigData;
	import HintSprite;
	import org.flixel.*;
	import PlayState;
	
	public class Human extends FlxSprite
	{
		public var isBeingCarried:Boolean = false;
		
		private var _age:Number = 1.0; // youngest age is 1.0
		private var _parentHuman:Human = null; // the parent human of this one
		private var _childHuman:Human = null; // the child of this human
		private var _playState:PlayState = null; // handle to state that created this instance
		private var _madeBaby:Boolean = false;
		private var _putDownTimestamp:uint = 0.0;
		private var _timeSinceLastHeart:Number = 0.0;
		private var _delayBetweenHearts:Number = ConfigData.DEFAULT_DELAY_BETWEEN_HEARTS;
		private var _heartTombstone:HeartTombstone = null;

		public var shirtColorIndex:int = 0;
		
		
		public function Human(playState:PlayState, ID:int, startX:int, startY:int):void
		{
			_playState = playState;
			super.x = startX;
			super.y = startY;
			super.ID = ID;
			acceleration.y = ConfigData.GRAVITY_ACCELERATION;
		}
		
		public function get madeBaby():Boolean
		{
			return _madeBaby;
		}
		
		public function set madeBaby(baby:Boolean):void
		{
			_madeBaby = baby;
		}
		
		// Procedurally draw the human based on age
		public function updateGraphics():void
		{
			if (_playState.doingWin)
				return;
			// Are you dead? Draw tombstone...
			if (Math.floor(_age) == ConfigData.AGE_7_DEAD) {
				this.loadGraphic(AssetManager.Tombstone);
				return;
			}
			
			// skin base
			makeGraphic(ConfigData.HUMAN_WIDTH, ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age), ConfigData.SKIN_COLOR); // background color (face)
			// Infants and Toddlers have diapers
			if (Math.floor(_age) <= ConfigData.AGE_1_INFANT) {
				drawLine(0,
						 int(ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) / 2.0),
						 ConfigData.HUMAN_WIDTH,
						 int(ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) / 2.0),
						 ConfigData.HUMAN_DIAPER_COLOR,
						 ConfigData.HUMAN_MIN_HEIGHT * ConfigData.DIAPER_HEIGHT);
			}

			// Teens and above have clothes
			if (Math.floor(_age) >= ConfigData.AGE_2_TODDLER) {  
				// hair
				drawLine(0,
						 Math.ceil(ConfigData.HUMAN_MIN_HEIGHT * ConfigData.HUMAN_HAIR_HEIGHT / 2.0),
						 ConfigData.HUMAN_WIDTH,
						 Math.ceil(ConfigData.HUMAN_MIN_HEIGHT * ConfigData.HUMAN_HAIR_HEIGHT / 2.0),
						 ConfigData.HUMAN_HAIR_COLOR_ARRAY[Math.floor(_age)-1],
						 Math.ceil(ConfigData.HUMAN_MIN_HEIGHT * ConfigData.HUMAN_HAIR_HEIGHT));

				// pants
				drawLine(0,
						 Math.floor(ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHOE_HEIGHT - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.PANTS_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER / 2.0),
						 ConfigData.HUMAN_WIDTH,
						 Math.floor(ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHOE_HEIGHT - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.PANTS_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER / 2.0),
						 ConfigData.HUMAN_PANTS_COLOR,
						 Math.floor(ConfigData.HUMAN_MIN_HEIGHT * ConfigData.PANTS_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER));
				// shirt
				drawLine(0,
						 Math.ceil(ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHOE_HEIGHT - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.PANTS_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHIRT_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER / 2.0),
						 ConfigData.HUMAN_WIDTH, 
						 Math.ceil(ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHOE_HEIGHT - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.PANTS_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHIRT_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER / 2.0),
						 ConfigData.AVAILABLE_SHIRT_COLORS[shirtColorIndex],
						 Math.ceil(ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHIRT_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) * ConfigData.CLOTHING_AGE_MULTIPLER));
				// shoes
				drawLine(0,
						 ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHOE_HEIGHT / 2.0,
						 ConfigData.HUMAN_WIDTH,
						 ConfigData.HUMAN_MIN_HEIGHT * Math.min(ConfigData.AGE_4_ADULT, _age) - ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHOE_HEIGHT / 2.0,
						 ConfigData.HUMAN_SHOE_COLOR,
						 ConfigData.HUMAN_MIN_HEIGHT * ConfigData.SHOE_HEIGHT);
			}
		}
		
		public function setParentHuman(parent:Human):void
		{
			_parentHuman = parent;
			shirtColorIndex = int(Math.random() * ConfigData.AVAILABLE_SHIRT_COLORS.length);
			while (shirtColorIndex == _parentHuman.shirtColorIndex)
			{
				shirtColorIndex = int(Math.random() * ConfigData.AVAILABLE_SHIRT_COLORS.length);
			}
		}
		

		public function setChildHuman(child:Human):void
		{
			_childHuman = child;
		}

		public function getParentHuman():Human
		{
			return _parentHuman;
		}

		public function getAge():Number
		{
			return _age;
		}
		
		public function getAgeFloored():int
		{
			return Math.floor(_age);
		}
		
		public function setAge(newState:Number):void
		{
			_age = newState;
			//trace("Set Human " + ID + " to age " + newState);
		}
		
		public function killHuman():void 
		{
			if (Math.floor(_age) == ConfigData.AGE_6_INVALID)
			{
				FlxG.play(AssetManager.FinalBreathSFX);
			}
			_age = ConfigData.AGE_7_DEAD;
			_playState.onHumanHitsAgeBoundary(this, int(ConfigData.AGE_7_DEAD));
			FlxG.play(AssetManager.TombstoneFallSFX);
			_heartTombstone.turnToTombstone();
		}
		
		public function tryToEmitHeart():void 
		{
			if (_parentHuman != null && _parentHuman.getAgeFloored() < ConfigData.AGE_7_DEAD)
				_parentHuman.tryToEmitHeart();
			
			// can I emit a heart?
			if ((getAgeFloored() < ConfigData.AGE_7_DEAD) && (_timeSinceLastHeart > _delayBetweenHearts))
			{
				_timeSinceLastHeart = 0.0;
				_playState.emitHeart(shirtColorIndex, this, _heartTombstone.getScreenXY());
				_heartTombstone.addHeart();
				_delayBetweenHearts = ConfigData.DEFAULT_DELAY_BETWEEN_HEARTS; // update loop will override this as needed for old humans
			}
		}
		
		public function setHeartTombstone( heartTombstone:HeartTombstone ):void
		{
			_heartTombstone = heartTombstone;
		}
		
		public override function update():void
		{
			if (_playState.doingWin)
				return;

			// update age
			if (!ConfigData.NO_AGING && !_playState.agingPaused && _age < ConfigData.MAX_AGE_DUE_TO_TIME)
			{
				var ageOld:Number = _age;
				_age += FlxG.elapsed * ConfigData.AGES_TO_AGE_FACTORS[int(_age)] * ConfigData.DEBUG_AGE_MULTIPLIER;
				//trace("Human " + ID + ": " + _age);
				if (Math.floor(ageOld) != Math.floor(_age))
				{
					// Hide the helpful hint if the infant turns to toddler
					if (Math.floor(_age) == ConfigData.AGE_2_TODDLER)
						_playState.pickupBabyHelpSprite.hideHint(FlxSprite(this));
					
					_playState.onHumanHitsAgeBoundary(this, Math.floor(_age));
				}
				if (!_madeBaby && _age > ConfigData.MAKE_A_BABY_AGE)
				{
					_playState.makeABaby(this);
				}
			}
			if (_playState.doingWin)
				return;

			// tracking for emiting hearts
			_timeSinceLastHeart += FlxG.elapsed;
			if ((_age > ConfigData.AGE_5_OLD_ADULT) && (_age < ConfigData.AGE_7_DEAD))
			{
				// recalculate delay until next heart based on distance from death.
				// I don't like doing this every frame, but need to so distance from
				// death makes hearts come faster without having to wait for next heart
				// to spawn based on old delay.
				var distanceToDeath:Number = Math.abs(_playState.death.x - this.x);
				//trace("distanceToDeath: " + distanceToDeath);
				distanceToDeath = Math.max(distanceToDeath, ConfigData.MIN_DISTANCE_TO_DEATH);
				distanceToDeath = Math.min(distanceToDeath, ConfigData.MAX_DISTANCE_TO_DEATH);
				//trace("distanceToDeath post clamping: " + distanceToDeath);
				var delayPercent:Number = (distanceToDeath - ConfigData.MIN_DISTANCE_TO_DEATH) / (ConfigData.MAX_DISTANCE_TO_DEATH - ConfigData.MIN_DISTANCE_TO_DEATH);
				//trace("delayPercent: " + delayPercent);
				_delayBetweenHearts = ConfigData.MIN_DELAY_BETWEEN_HEARTS + (delayPercent) * (ConfigData.DEFAULT_DELAY_BETWEEN_HEARTS - ConfigData.MIN_DELAY_BETWEEN_HEARTS);
				//trace("_delayBetweenHearts: " + _delayBetweenHearts);
			}
			
			// Update graphics of human (clothes and what-not)
			updateGraphics();
			
			// apply input from the two ages that bound the current age
			var ageLowerBound:int = Math.floor(_age);
			var ageUpperBound:int = Math.ceil(_age);
			
			_doMovement(ageLowerBound, ageUpperBound);
			
			// handle if being carried
			if (isBeingCarried)
			{
				tryToEmitHeart();
				// automatically put down if beyond infant age
				if (getAgeFloored() > ConfigData.AGE_1_INFANT)
				{
					putdown(_parentHuman.velocity.x);
				}
				else
				{
					// use parent's position
					x = _parentHuman.x + ConfigData.CARRIED_INFANT_X_OFFSET;
					y = _parentHuman.y + ConfigData.CARRIED_INFANT_Y_OFFSET;
				}
			}
			else 
			{	// check if close enough to emit a heart
				if (_parentHuman != null && overlaps(_parentHuman))
				{
					if (_parentHuman.getAgeFloored() < ConfigData.AGE_7_DEAD)
					{
						tryToEmitHeart();
					}
					
					// check for being picked up
					// check if all conditions for being picked up are met
					if ((ageLowerBound == ConfigData.AGE_1_INFANT) && // this human is infant
						(_parentHuman.getAgeFloored() == ConfigData.AGE_4_ADULT))// parent is adult
					{
						// if key is pressed, get picked up
						if (FlxG.keys.justPressed("DOWN"))
						{
							pickup();
							_playState.pickupBabyHelpSprite.hideHint(FlxSprite(this));
						}
						else
						{
							// else, show hint visuals
							_playState.pickupBabyHelpSprite.showHint(-15, FlxG.height/2, this);
						}
					}
				}
			}
			
			// if carrying an infant, check for putting infant down
			if ((_childHuman != null) && (_childHuman.isBeingCarried) && (FlxG.keys.justPressed("DOWN")))
				_childHuman.putdown(velocity.x);
				
			super.update();
		}
		
		private function _doMovement(ageLowerBound:int, ageUpperBound:int):void
		{
			acceleration.x = 0.0;
			
			// set max velocity, acceleration, and jump velocity by lerping between values defined for bound ages
			var toNextBoundPercent:Number = _age - ageLowerBound;
			var accelX1:Number = ConfigData.AGE_LIMITS[ageLowerBound][0];
			var accelX2:Number = ConfigData.AGE_LIMITS[ageUpperBound][0];
			var accelX:Number = accelX1 + toNextBoundPercent * (accelX2 - accelX1);
			
			var maxVel1:Number = ConfigData.AGE_LIMITS[ageLowerBound][1];
			var maxVel2:Number = ConfigData.AGE_LIMITS[ageUpperBound][1];
			maxVelocity.x = maxVel1 + toNextBoundPercent * (maxVel2 - maxVel1);
			
			var jumpVel1:Number = ConfigData.AGE_LIMITS[ageLowerBound][2];
			var jumpVel2:Number = ConfigData.AGE_LIMITS[ageUpperBound][2];
			var jumpVelocity:Number = jumpVel1 + toNextBoundPercent * (jumpVel2 - jumpVel1);
			
			// update drag to be some reasonable value
			drag.x = accelX*0.8;
			
			// handle left/right movement
			switch(ageLowerBound)
			{
				case ConfigData.AGE_1_INFANT:
					// infants don't move, so do nothing
				break;
				case ConfigData.AGE_2_TODDLER:
					// toddlers move towards parent human in x
					if (_parentHuman != null)
					{
						if ( _parentHuman.x > x)
							acceleration.x = accelX;
						else
							acceleration.x = -accelX;
					}
				break;
				case ConfigData.AGE_3_TEEN:
					// can move in x
					if(FlxG.keys.LEFT)
						acceleration.x = -accelX;
					if(FlxG.keys.RIGHT)
						acceleration.x = accelX;
				break;
			    case ConfigData.AGE_4_ADULT:
					// can move in x
					if(FlxG.keys.LEFT)
						acceleration.x = -accelX;
					if(FlxG.keys.RIGHT)
						acceleration.x = accelX;
				break;
				case ConfigData.AGE_5_OLD_ADULT:
					// if child is alive only listen to keyboard movement that takes
					// you closer to child
					if (_childHuman != null && _childHuman.getAgeFloored() < ConfigData.AGE_7_DEAD)
					{
						if(FlxG.keys.LEFT && (_childHuman.x < x))
							acceleration.x = -accelX;
						else if(FlxG.keys.RIGHT && (_childHuman.x > x))
							acceleration.x = accelX;
					}
					else // allow free movement
					{
						if(FlxG.keys.LEFT)
							acceleration.x = -accelX;
						if(FlxG.keys.RIGHT)
							acceleration.x = accelX;
					}
				break;
				case ConfigData.AGE_6_INVALID:
					// if child is alive only listen to keyboard movement that takes
					// you closer to child
					if (_childHuman != null && _childHuman.getAgeFloored() < ConfigData.AGE_7_DEAD)
					{
						if(FlxG.keys.LEFT && (_childHuman.x < x))
							acceleration.x = -accelX;
						else if(FlxG.keys.RIGHT && (_childHuman.x > x))
							acceleration.x = accelX;
					}
					else // allow free movement
					{
						if(FlxG.keys.LEFT)
							acceleration.x = -accelX;
						if(FlxG.keys.RIGHT)
							acceleration.x = accelX;
					}
				break;
				case ConfigData.AGE_7_DEAD:
					// dead don't move, so do nothing
				break;
			}
			
			// trigger jump
			if(FlxG.keys.justPressed("UP") && isTouching(FlxObject.FLOOR))
				velocity.y = -jumpVelocity;
		}
		
		public function pickup():void
		{	
			// only pickup after a certain delay has passed since the last putdown
			if ((FlxU.getTicks() - _putDownTimestamp) > ConfigData.MIN_MILLISECONDS_BETWEEN_PUTDOWN_PICKUP)
			{
				// turn off gravity
				acceleration.y = 0.0;
				
				// set flag indicating we are being carried
				isBeingCarried = true;
				FlxG.play(AssetManager.BabyUpSFX);
				//trace("picked up infant");
			}
		}
		
		public function putdown(velX:Number):void
		{
			// restore gravity
			acceleration.y = ConfigData.GRAVITY_ACCELERATION;
			
			isBeingCarried = false;
			
			// tell hint sprite so it can clean up
			_playState.pickupBabyHelpSprite.hideHint(FlxSprite(this));
			
			_putDownTimestamp = FlxU.getTicks();
			
			velocity.x = velX;
			if (Math.floor(_age) == ConfigData.AGE_1_INFANT)
			{
				FlxG.play(AssetManager.BabyDownSFX);
			} else {
				FlxG.play(AssetManager.ToddlerDownSFX);
			}
			//trace("put infant down");
		}
	}
}